/* -------------------------------------------------------------------------
 *
 * pgstat_database.c
 *	  数据库统计的实现。
 *
 * 本文件包含数据库统计的实现。它与pgstat.c分开，以强制统计访问/存储实现与各类型统计的细节之间的界限。
 *
 * Copyright (c) 2001-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  src/backend/utils/activity/pgstat_database.c
 * -------------------------------------------------------------------------
 */

#include "postgres.h"

#include "utils/pgstat_internal.h"
#include "utils/timestamp.h"
#include "storage/procsignal.h"


static bool fc_pgstat_should_report_connstat(void);


PgStat_Counter pgStatBlockReadTime = 0;
PgStat_Counter pgStatBlockWriteTime = 0;
PgStat_Counter pgStatActiveTime = 0;
PgStat_Counter pgStatTransactionIdleTime = 0;
SessionEndType pgStatSessionEndCause = DISCONNECT_NORMAL;


static int	pgStatXactCommit = 0;
static int	pgStatXactRollback = 0;
static PgStat_Counter pgLastSessionReportTime = 0;


/*
 * 删除被丢弃的数据库的条目。
 */
void pgstat_drop_database(Oid fc_databaseid)
{
	pgstat_drop_transactional(PGSTAT_KIND_DATABASE, fc_databaseid, InvalidOid);
}

/*
 * 从 autovacuum.c 调用以报告 autovacuum 进程的启动。
 * 在 InitPostgres 完成之前被调用，因此无法依赖 MyDatabaseId；
 * 必须传入数据库 OID。
 */
void pgstat_report_autovac(Oid fc_dboid)
{
	PgStat_EntryRef *fc_entry_ref;
	PgStatShared_Database *fc_dbentry;

	/* 单用户模式下不能到达这里 */
	Assert(IsUnderPostmaster);

	/*
	 * 真空结束即时报告。为了一致性，以相同的方式报告开始。
	 * 真空运行不频繁且耗时较长，因此在这里被阻塞一点也没关系。
	 */
	fc_entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE,
											fc_dboid, InvalidOid, false);

	fc_dbentry = (PgStatShared_Database *) fc_entry_ref->shared_stats;
	fc_dbentry->stats.last_autovac_time = GetCurrentTimestamp();

	pgstat_unlock_entry(fc_entry_ref);
}

/*
 * 报告热备份恢复冲突。
 */
void pgstat_report_recovery_conflict(int fc_reason)
{
	PgStat_StatDBEntry *fc_dbentry;

	Assert(IsUnderPostmaster);
	if (!pgstat_track_counts)
		return;

	fc_dbentry = pgstat_prep_database_pending(MyDatabaseId);

	switch (fc_reason)
	{
		case PROCSIG_RECOVERY_CONFLICT_DATABASE:

			/*
			 * 由于我们在信息复制后立即删除数据库信息，
			 * 因此计算这些冲突没有意义。
			 */
			break;
		case PROCSIG_RECOVERY_CONFLICT_TABLESPACE:
			fc_dbentry->n_conflict_tablespace++;
			break;
		case PROCSIG_RECOVERY_CONFLICT_LOCK:
			fc_dbentry->n_conflict_lock++;
			break;
		case PROCSIG_RECOVERY_CONFLICT_SNAPSHOT:
			fc_dbentry->n_conflict_snapshot++;
			break;
		case PROCSIG_RECOVERY_CONFLICT_BUFFERPIN:
			fc_dbentry->n_conflict_bufferpin++;
			break;
		case PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK:
			fc_dbentry->n_conflict_startup_deadlock++;
			break;
	}
}

/*
 * 报告检测到的死锁。
 */
void pgstat_report_deadlock(void)
{
	PgStat_StatDBEntry *fc_dbent;

	if (!pgstat_track_counts)
		return;

	fc_dbent = pgstat_prep_database_pending(MyDatabaseId);
	fc_dbent->n_deadlocks++;
}

/*
 * 报告一个或多个校验和失败。
 */
void pgstat_report_checksum_failures_in_db(Oid fc_dboid, int fc_failurecount)
{
	PgStat_EntryRef *fc_entry_ref;
	PgStatShared_Database *fc_sharedent;

	if (!pgstat_track_counts)
		return;

	/*
	 * 直接更新共享统计信息 - 校验和失败不应足够常见以至于成为问题。
	 */
	fc_entry_ref =
		pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE, fc_dboid, InvalidOid, false);

	fc_sharedent = (PgStatShared_Database *) fc_entry_ref->shared_stats;
	fc_sharedent->stats.n_checksum_failures += fc_failurecount;
	fc_sharedent->stats.last_checksum_failure = GetCurrentTimestamp();

	pgstat_unlock_entry(fc_entry_ref);
}

/*
 * 报告当前数据库中的一个校验和失败。
 */
void pgstat_report_checksum_failure(void)
{
	pgstat_report_checksum_failures_in_db(MyDatabaseId, 1);
}

/*
 * 报告创建临时文件。
 */
void pgstat_report_tempfile(size_t fc_filesize)
{
	PgStat_StatDBEntry *fc_dbent;

	if (!pgstat_track_counts)
		return;

	fc_dbent = pgstat_prep_database_pending(MyDatabaseId);
	fc_dbent->n_temp_bytes += fc_filesize;
	fc_dbent->n_temp_files++;
}

/*
 * 通知统计系统新的连接。
 */
void pgstat_report_connect(Oid fc_dboid)
{
	PgStat_StatDBEntry *fc_dbentry;

	if (!fc_pgstat_should_report_connstat())
		return;

	pgLastSessionReportTime = MyStartTimestamp;

	fc_dbentry = pgstat_prep_database_pending(MyDatabaseId);
	fc_dbentry->n_sessions++;
}

/*
 * 通知统计系统断开连接。
 */
void pgstat_report_disconnect(Oid fc_dboid)
{
	PgStat_StatDBEntry *fc_dbentry;

	if (!fc_pgstat_should_report_connstat())
		return;

	fc_dbentry = pgstat_prep_database_pending(MyDatabaseId);

	switch (pgStatSessionEndCause)
	{
		case DISCONNECT_NOT_YET:
		case DISCONNECT_NORMAL:
			/* 我们不收集这些 */
			break;
		case DISCONNECT_CLIENT_EOF:
			fc_dbentry->n_sessions_abandoned++;
			break;
		case DISCONNECT_FATAL:
			fc_dbentry->n_sessions_fatal++;
			break;
		case DISCONNECT_KILLED:
			fc_dbentry->n_sessions_killed++;
			break;
	}
}

/*
 * SQL 可调用的 pgstat* 函数的支持函数。返回一个数据库的收集统计信息或 NULL。NULL 不意味着
 * 数据库不存在，只是没有统计信息，因此调用者最好报告零。
 */
PgStat_StatDBEntry *
pgstat_fetch_stat_dbentry(Oid fc_dboid)
{
	return (PgStat_StatDBEntry *)
		pgstat_fetch_entry(PGSTAT_KIND_DATABASE, fc_dboid, InvalidOid);
}

void AtEOXact_PgStat_Database(bool fc_isCommit, bool fc_parallel)
{
	/* 不计算并行工作者事务统计 */
	if (!fc_parallel)
	{
		/*
		 * 计数事务提交或中止。 （我们使用计数器，而不仅仅是布尔值，以防报告消息未立即发送。）
		 */
		if (fc_isCommit)
			pgStatXactCommit++;
		else
			pgStatXactRollback++;
	}
}

/*
 * pgstat_report_stat() 的子例程：处理 xact 提交/回滚和 I/O
 * 时序。
 */
void pgstat_update_dbstats(TimestampTz fc_ts)
{
	PgStat_StatDBEntry *fc_dbentry;

	/*
	 * 如果尚未连接到数据库，则不要将时间归因于“共享
	 * 状态”（使用 InvalidOid 跟踪共享关系等的统计信息）。
	 */
	if (!OidIsValid(MyDatabaseId))
		return;

	fc_dbentry = pgstat_prep_database_pending(MyDatabaseId);

	/*
	 * 将 xact 提交/回滚和 I/O 时序累积到当前数据库的统计条目中。
	 */
	fc_dbentry->n_xact_commit += pgStatXactCommit;
	fc_dbentry->n_xact_rollback += pgStatXactRollback;
	fc_dbentry->n_block_read_time += pgStatBlockReadTime;
	fc_dbentry->n_block_write_time += pgStatBlockWriteTime;

	if (fc_pgstat_should_report_connstat())
	{
		long		fc_secs;
		int			fc_usecs;

		/*
		 * pgLastSessionReportTime 由 pgstat_report_connect()
		 * 初始化为 MyStartTimestamp。
		 */
		TimestampDifference(pgLastSessionReportTime, fc_ts, &fc_secs, &fc_usecs);
		pgLastSessionReportTime = fc_ts;
		fc_dbentry->total_session_time += (PgStat_Counter) fc_secs * 1000000 + fc_usecs;
		fc_dbentry->total_active_time += pgStatActiveTime;
		fc_dbentry->total_idle_in_xact_time += pgStatTransactionIdleTime;
	}

	pgStatXactCommit = 0;
	pgStatXactRollback = 0;
	pgStatBlockReadTime = 0;
	pgStatBlockWriteTime = 0;
	pgStatActiveTime = 0;
	pgStatTransactionIdleTime = 0;
}

/*
 * 我们只报告正常后端进程的会话统计信息。并行
 * 工作者并行运行，因此它们并不贡献于会话时间，即使
 * 它们使用了 CPU 时间。Walsender 进程可以被视为这里，
 * 但它们具有不同于正常后端的会话特征（例如，它们总是“活跃”的），
 * 因此会扭曲会话统计信息。
 */
static bool fc_pgstat_should_report_connstat(void)
{
	return MyBackendType == B_BACKEND;
}

/*
 * 查找或为 dboid 创建一个本地 PgStat_StatDBEntry 条目。
 */
PgStat_StatDBEntry *
pgstat_prep_database_pending(Oid fc_dboid)
{
	PgStat_EntryRef *fc_entry_ref;

	/*
	 * 在连接到数据库之前，不应报告数据库对象的统计信息。
	 */
	Assert(!OidIsValid(fc_dboid) || OidIsValid(MyDatabaseId));

	fc_entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_DATABASE, fc_dboid, InvalidOid,
										  NULL);

	return fc_entry_ref->pending;
}

/*
 * 重置数据库的重置时间戳，而不重置数据库统计信息的内容。
 */
void pgstat_reset_database_timestamp(Oid fc_dboid, TimestampTz fc_ts)
{
	PgStat_EntryRef *fc_dbref;
	PgStatShared_Database *fc_dbentry;

	fc_dbref = pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE, MyDatabaseId, InvalidOid,
										false);

	fc_dbentry = (PgStatShared_Database *) fc_dbref->shared_stats;
	fc_dbentry->stats.stat_reset_timestamp = fc_ts;

	pgstat_unlock_entry(fc_dbref);
}

/*
 * 刷新条目的待处理统计信息
 *
 * 如果 nowait 为真，如果无法立即获取锁，则该函数返回 false，
 * 否则返回 true。
 */
bool pgstat_database_flush_cb(PgStat_EntryRef *fc_entry_ref, bool fc_nowait)
{
	PgStatShared_Database *fc_sharedent;
	PgStat_StatDBEntry *fc_pendingent;

	fc_pendingent = (PgStat_StatDBEntry *) fc_entry_ref->pending;
	fc_sharedent = (PgStatShared_Database *) fc_entry_ref->shared_stats;

	if (!pgstat_lock_entry(fc_entry_ref, fc_nowait))
		return false;

#define PGSTAT_ACCUM_DBCOUNT(item)		\
	(fc_sharedent)->stats.item += (fc_pendingent)->item

	PGSTAT_ACCUM_DBCOUNT(n_xact_commit);
	PGSTAT_ACCUM_DBCOUNT(n_xact_rollback);
	PGSTAT_ACCUM_DBCOUNT(n_blocks_fetched);
	PGSTAT_ACCUM_DBCOUNT(n_blocks_hit);

	PGSTAT_ACCUM_DBCOUNT(n_tuples_returned);
	PGSTAT_ACCUM_DBCOUNT(n_tuples_fetched);
	PGSTAT_ACCUM_DBCOUNT(n_tuples_inserted);
	PGSTAT_ACCUM_DBCOUNT(n_tuples_updated);
	PGSTAT_ACCUM_DBCOUNT(n_tuples_deleted);

	/* last_autovac_time 立即报告 */
	Assert(fc_pendingent->last_autovac_time == 0);

	PGSTAT_ACCUM_DBCOUNT(n_conflict_tablespace);
	PGSTAT_ACCUM_DBCOUNT(n_conflict_lock);
	PGSTAT_ACCUM_DBCOUNT(n_conflict_snapshot);
	PGSTAT_ACCUM_DBCOUNT(n_conflict_bufferpin);
	PGSTAT_ACCUM_DBCOUNT(n_conflict_startup_deadlock);

	PGSTAT_ACCUM_DBCOUNT(n_temp_bytes);
	PGSTAT_ACCUM_DBCOUNT(n_temp_files);
	PGSTAT_ACCUM_DBCOUNT(n_deadlocks);

	/* checksum 失败立即报告 */
	Assert(fc_pendingent->n_checksum_failures == 0);
	Assert(fc_pendingent->last_checksum_failure == 0);

	PGSTAT_ACCUM_DBCOUNT(n_block_read_time);
	PGSTAT_ACCUM_DBCOUNT(n_block_write_time);

	PGSTAT_ACCUM_DBCOUNT(n_sessions);
	PGSTAT_ACCUM_DBCOUNT(total_session_time);
	PGSTAT_ACCUM_DBCOUNT(total_active_time);
	PGSTAT_ACCUM_DBCOUNT(total_idle_in_xact_time);
	PGSTAT_ACCUM_DBCOUNT(n_sessions_abandoned);
	PGSTAT_ACCUM_DBCOUNT(n_sessions_fatal);
	PGSTAT_ACCUM_DBCOUNT(n_sessions_killed);
#undef PGSTAT_ACCUM_DBCOUNT

	pgstat_unlock_entry(fc_entry_ref);

	memset(fc_pendingent, 0, sizeof(*fc_pendingent));

	return true;
}

void pgstat_database_reset_timestamp_cb(PgStatShared_Common *fc_header, TimestampTz fc_ts)
{
	((PgStatShared_Database *) fc_header)->stats.stat_reset_timestamp = fc_ts;
}
